package com.ddxz.tool.web.component;

import com.alibaba.fastjson.JSON;
import com.ddxz.tool.core.convert.CollectionUtil;
import com.ddxz.tool.core.convert.DateUtil;
import com.ddxz.tool.core.convert.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;

/**
 * @author 朱宗刚
 * @version 1.0.0
 * @date 2020/2/28
 */

@Aspect
@Order(-1)
@Component
public class DdxzLogAspect {
    private static final Logger log = LoggerFactory.getLogger(DdxzLogAspect.class);

    public DdxzLogAspect(String paths) {
        ignorePaths = new HashSet<>(StringUtil.getList(paths));
    }

    @AfterReturning(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)", returning = "rst")
    public void afterReturn(JoinPoint point, Object rst) {
        if(ignorePaths.contains(point.getSignature().getDeclaringTypeName())) {
            return;
        }

        if(start == null || start.get() == null) {
            return;
        }

        StringBuilder builder = new StringBuilder();
        builder.append(point.getSignature().getDeclaringType().getSimpleName())
                .append(QUOTE_BEGIN)
                .append(point.getSignature().getName())
                .append(QUOTE_END);
        if(isController(point)) {
            builder.append(INTERVAL).append(DateUtil.intervalMilliSecond(start.get()));
            start.remove();
        }

        if(log.isDebugEnabled()) {
            builder.append(RESULT).append(QUOTE_BEGIN)
                    .append(JSON.toJSONString(rst))
                    .append(QUOTE_END);
            log.debug("after return with result: {}", builder);
        } else {
            log.info("after return: {}", builder);
        }

    }

    @Before(value = "@annotation(request)")
    public void beforeAspect(JoinPoint point, RequestMapping request) {
        if(CollectionUtil.isEmpty(request.method()) || CollectionUtil.isEmpty(request.value())) {
            return;
        }

        beforeAspect(point, String.format("%s: %s", request.method()[0].name(), request.value()[0]));
    }

    private void beforeAspect(JoinPoint point, String url) {
        if(ignorePaths.contains(point.getSignature().getDeclaringTypeName())) {
            return;
        }

        if(isController(point)) {
            start.set(DateUtil.getTimestamp());
        }

        StringBuilder builder = new StringBuilder();
        builder.append(point.getSignature().getDeclaringType().getSimpleName())
                .append(QUOTE_BEGIN)
                .append(point.getSignature().getName())
                .append(QUOTE_END);

        if(log.isDebugEnabled()) {
            builder.append(StringUtil.join(point.getArgs(), DELIMITER));
            log.debug("{} before invoke detail {}", url, builder.toString());
        } else {
            log.info("{} before invoke {}", url, builder);
        }
    }

    private boolean isController(JoinPoint point) {
        for(Annotation ann : point.getSignature().getDeclaringType().getAnnotations()) {
            if(ann.annotationType() == RestController.class) {
                return true;
            }

            if(ann.annotationType() == Controller.class) {
                return true;
            }
        }

        return false;
    }

    @Before(value = "@annotation(mapping)")
    public void beforeGetMethod(JoinPoint point, GetMapping mapping) {
        if(CollectionUtil.isEmpty(mapping.value())) {
            return ;
        }

        beforeAspect(point, String.format("GET: %s", mapping.value()[0]));
    }

    @AfterReturning(value = "@annotation(org.springframework.web.bind.annotation.GetMapping)", returning = "rst")
    public void afterGetMethod(JoinPoint point, Object rst) {
        afterReturn(point, rst);
    }

    @Before(value = "@annotation(mapping)")
    public void beforePostMethod(JoinPoint point, PostMapping mapping) {
        if(CollectionUtil.isEmpty(mapping.value())) {
            return ;
        }

        beforeAspect(point, String.format("POST: %s", mapping.value()[0]));
    }

    @AfterReturning(value = "@annotation(org.springframework.web.bind.annotation.PostMapping)", returning = "rst")
    public void afterPostMethod(JoinPoint point, Object rst) {
        afterReturn(point, rst);
    }

    @Before(value = "@annotation(mapping)")
    public void beforePutMethod(JoinPoint point, PutMapping mapping) {
        if(CollectionUtil.isEmpty(mapping.value())) {
            return ;
        }

        beforeAspect(point, String.format("PUT: %s", mapping.value()[0]));
    }

    @AfterReturning(value = "@annotation(org.springframework.web.bind.annotation.PutMapping)", returning = "rst")
    public void afterPutMethod(JoinPoint point, Object rst) {
        afterReturn(point, rst);
    }

    @Before(value = "@annotation(mapping)")
    public void beforeDelMethod(JoinPoint point, DeleteMapping mapping) {
        if(CollectionUtil.isEmpty(mapping.value())) {
            return ;
        }

        beforeAspect(point, String.format("DELETE: %s", mapping.value()[0]));
    }

    @AfterReturning(value = "@annotation(org.springframework.web.bind.annotation.DeleteMapping)", returning = "rst")
    public void afterDelMethod(JoinPoint point, Object rst) {
        afterReturn(point, rst);
    }

    private Set<String> ignorePaths;

    private ThreadLocal<Long> start = new ThreadLocal<>();

    private static final String QUOTE_BEGIN = "(";
    private static final String QUOTE_END   = ") ";
    private static final String DELIMITER   = ", ";
    private static final String RESULT      = "RESULT ";
    private static final String INTERVAL    = "INTERVAL ";
}
